/*
 * Galaxium Messenger
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;

using Mono.Addins;

using Gtk;
using Pango;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Gui;
using Galaxium.Protocol;

namespace Galaxium.Gui.GtkGui
{
	public static class GtkUtility
	{
		private static bool _initialized;
		private static bool _enableSwf;		
		
		public static bool EnableSwf
		{
			get { return _enableSwf; }
		}
		
		public static void Initialize ()
		{
			if (!_initialized)
			{
				IconUtility.Initialize ();
			
				try
				{
					Swfdec.Swfdec.Initialize ();
					_enableSwf = true;
				}
				catch (DllNotFoundException ex)
				{
					Log.Info ("Unable to find {0}, swf playback disabled", ex.Message);
				}
				
				_initialized = true;
			}
		}
		
		public static void CheckThreading ()
		{
#if DEBUG
			if (System.Threading.Thread.CurrentThread.Name != CoreUtility.ApplicationName)
			{
				StackTrace trace = new StackTrace(1, true);
				Log.Fatal ("Not executing in main thread:\n"+trace.ToString());
			}
#endif
		}
		
		public static AccelKey ParseAccelKey (string accelKey)
		{
			if (String.IsNullOrEmpty (accelKey))
				return AccelKey.Zero;
			
			Gdk.Key key = Gdk.Key.VoidSymbol;
			
			string[] split = accelKey.Split ('+');
			
			if (split.Length == 1)
			{
				//it's a key alone, eg F7
				if (TryParseKey (split[0], out key))
					return new AccelKey (key, Gdk.ModifierType.None, AccelFlags.Visible);
			}
			else if (split.Length == 2)
			{
				//modifier+key
				Gdk.ModifierType modifiers = ParseModifier (split[0]);
				
				if (TryParseKey (split[1], out key))
					return new AccelKey (key, modifiers, AccelFlags.Visible);
			}
			else if (split.Length == 3)
			{
				//modifier+modifier+key
				Gdk.ModifierType modifiers = ParseModifier (split[0]);
				
				modifiers |= ParseModifier (split[1]);
				
				if (TryParseKey (split[2], out key))
					return new AccelKey (key, modifiers, AccelFlags.Visible);
			}
			return AccelKey.Zero;
		}
		
		public static bool TryParseKey (string keyString, out Gdk.Key key)
		{
			try
			{
				key = (Gdk.Key)Enum.Parse (typeof (Gdk.Key), keyString);
				return true;
			}
			catch (ArgumentException)
			{
				
			}
			
			key = Gdk.Key.VoidSymbol;
			
			return false;
		}
		
		public static Gdk.ModifierType ParseModifier (string modifierString)
		{
			if (String.IsNullOrEmpty (modifierString))
				return Gdk.ModifierType.None;
			
			if (String.Compare (modifierString, "control", true) == 0 || String.Compare (modifierString, "control", true) == 0)
				return Gdk.ModifierType.ControlMask;
			else if (String.Compare (modifierString, "shift", true) == 0)
				return Gdk.ModifierType.ShiftMask;
			else if (String.Compare (modifierString, "alt", true) == 0)
				return Gdk.ModifierType.Mod1Mask;
			
			return Gdk.ModifierType.None;
		}
		
		public static void RenderFormatted (IEnumerable<IMessageChunk> chunks, IEntity entity, bool showEmoticons,
		                                    Gtk.Style style, Gtk.StateType state, Cairo.Context cx,
		                                    Pango.Context pcx,
		                                    double startx, double starty, double w, double h)
		{
			double x = startx;
			double y = starty;
			
			FontMetrics metrics = pcx.GetMetrics (pcx.FontDescription, pcx.Language);
			
			double normLineHeight = (metrics.Ascent + metrics.Descent) / Pango.Scale.PangoScale;
			double lineHeight = normLineHeight;
			
			metrics.Dispose ();
			
			cx.Save ();
			
			//cx.Rectangle (x, y, w, h);
			//cx.Clip ();
			
			RenderChunks (chunks, entity, showEmoticons, style, state, cx, pcx, ref x, ref y, w, h, ref lineHeight, normLineHeight, startx, starty);
			
			cx.Restore ();
		}
		
		static void RenderChunks (IEnumerable<IMessageChunk> chunks, IEntity entity, bool showEmoticons, Gtk.Style style, Gtk.StateType state,
		                          Cairo.Context cx, Pango.Context pcx, ref double x, ref double y, double w, double h,
		                          ref double lineHeight, double normLineHeight, double startx, double starty)
		{
			foreach (IMessageChunk chunk in chunks)
			{
				
				if (chunk is IEmoticonMessageChunk && showEmoticons)
				{
					Gdk.Pixbuf pbuf = null;
					
					try
					{
						pbuf = new Gdk.Pixbuf ((chunk as IEmoticonMessageChunk).Emoticon.Data);
					}
					catch { }
					
					if (pbuf == null)
						continue;
					
					int pW = Math.Max ((int)(lineHeight * (double)pbuf.Width / (double)pbuf.Height), 1);
					int pH = Math.Max ((int)lineHeight, 1);
					
					if (x + pW < startx + w)
					{
						Gdk.Pixbuf scaled = pbuf.ScaleSimple (pW, pH, Gdk.InterpType.Bilinear);
						Gdk.CairoHelper.SetSourcePixbuf (cx, scaled, x, y);
						cx.Paint ();
						scaled.Dispose ();
					}
					
					x += pW;
					
					pbuf.Dispose ();
				}
				else if (chunk is ITextMessageChunk && !string.IsNullOrEmpty ((chunk as ITextMessageChunk).Text))
				{
					if (((chunk as ITextMessageChunk).Text == "\n") ||
					    ((chunk as ITextMessageChunk).Text == "\r\n") ||
					    ((chunk as ITextMessageChunk).Text == "\r"))
					{
						x = startx;
						y += lineHeight;
						lineHeight = normLineHeight;
						continue;
					}
					
					Pango.Layout ly = new Pango.Layout (pcx);
					ly.FontDescription = pcx.FontDescription.Copy ();
					ly.SetMarkup ((chunk as ITextMessageChunk).Text);
					ly.Width = Pango.Units.FromPixels ((int)(startx + w - x));
					ly.Ellipsize = EllipsizeMode.End;
					
					//TODO: Using pango attributes seems to cause a crash ?!?
					
					//if (chunk is IURIMessageChunk)
					//	ly.Attributes.Change (new AttrUnderline (Underline.Single));
					
					if (chunk.Style != null)
					{
						if (!string.IsNullOrEmpty (chunk.Style.Font))
							ly.FontDescription.Family = chunk.Style.Font;
						
						if (chunk.Style.FontSize > 0)
							ly.FontDescription.Size = chunk.Style.FontSize;
						
						if (chunk.Style.Bold)
							ly.FontDescription.Weight = Pango.Weight.Bold;
						
						if (chunk.Style.Italic)
							ly.FontDescription.Style = Pango.Style.Italic;
						
						//if (chunk.Style.Underline && !(chunk is IURIMessageChunk))
						//	ly.Attributes.Change (new AttrUnderline (Underline.Single));
						
						//if (chunk.Style.Strikethrough)
						//	ly.Attributes.Change (new AttrStrikethrough (true));
					}
					
					int pW, pH;
					ly.GetPixelSize (out pW, out pH);
					
					if ((chunk.Style != null) && (chunk.Style.Background != ARGBGradient.None))
					{
						ARGBColor col = chunk.Style.Background;
						
						cx.Color = new Cairo.Color ((double)col.R / 255,
						                            (double)col.G / 255,
						                            (double)col.B / 255,
						                            (double)col.A / 255);
						
						cx.Rectangle (x, y, pW, pH);
						cx.Fill ();
					}
					
					if ((chunk.Style != null) && (chunk.Style.Foreground != ARGBGradient.None))
					{
					// CHECKTHIS	
					//	ARGBColor col = chunk.Style.Foreground;
						//ly.Attributes.Change (new AttrForeground ((ushort)(col.R << 8),
						//                                          (ushort)(col.G << 8),
						//                                          (ushort)(col.B << 8)));
						
						//cx.Color = new Cairo.Color ((double)col.R / 255,
						//                            (double)col.G / 255,
						//                            (double)col.B / 255,
						//                            (double)col.A / 255);
					}
					else if (chunk is IURIMessageChunk)
					{
						//TODO: From theme?
						//ly.Attributes.Change (new AttrForeground (0, 0, ushort.MaxValue));
					}
					else
						Gdk.CairoHelper.SetSourceColor (cx, style.Text (state));
					
					if (x < startx + w)
					{
						cx.MoveTo (x, y);
						
						//Pango.CairoHelper.UpdateLayout (cx, ly);
						Pango.CairoHelper.ShowLayout (cx, ly);
					}
					
					ly.Dispose ();
					
					x += pW;
					lineHeight = Math.Max (lineHeight, pH);
				}
				
				RenderChunks (chunk.Chunks, entity, showEmoticons, style, state, cx, pcx, ref x, ref y, w, h, ref lineHeight, normLineHeight, startx, starty);
			}
		}
		
		public static Gtk.Image LoadImage (byte[] data)
		{
			try
			{
				Gdk.PixbufAnimation anim = null;
				Gdk.Pixbuf pixbuf = null;
				
				if ((data != null) && (data.Length > 0))
				{
					Gdk.PixbufLoader loader = new Gdk.PixbufLoader (data);
						
					if (loader.Animation.IsStaticImage)
						pixbuf = loader.Pixbuf;
					else
						anim = loader.Animation;
				}	
				else	
					pixbuf = IconUtility.GetIcon ("galaxium-image");
				
				Gtk.Image image;
				
				if (anim != null)
					image = new Gtk.Image (anim);
				else
					image = new Gtk.Image (pixbuf);
				
				image.Visible = true;
				
				return image;
			}
			catch (Exception)
			{
				return new Gtk.Image (IconUtility.GetIcon ("galaxium-image"));
			}
		}
		
		public static Gtk.Image LoadEmoticon (IEmoticon emot)
		{
			return LoadImage (emot.Data);
		}
		
		public static void EnableComposite (Gtk.Widget widget)
		{
			if (widget.Screen.RgbaColormap != null)
				widget.Colormap = widget.Screen.RgbaColormap;
		}
	}
}